package net.scribblemedia.candle.mlengine;

import java.io.Serializable;

import net.scribblemedia.candle.data.trainingset.TrainingSetHolder;

import org.apache.log4j.Logger;
import org.encog.engine.network.activation.ActivationLOG;
import org.encog.engine.network.activation.ActivationSigmoid;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.basic.BasicMLDataSet;
import org.encog.ml.train.MLTrain;
import org.encog.neural.networks.BasicNetwork;
import org.encog.neural.networks.layers.BasicLayer;
import org.encog.neural.networks.training.propagation.resilient.ResilientPropagation;
 
public class FlexibleNetwork implements Serializable {
	
	private static final long serialVersionUID = 1L;

	private static Logger LOG = Logger.getLogger(FlexibleNetwork.class); 
 
	private MLDataSet trainingSet;
	private BasicNetwork network;

	public FlexibleNetwork(TrainingSetHolder trainingSetHolder) {
		this(trainingSetHolder, new BasicLayer(new ActivationLOG(), true, 30), new BasicLayer(new ActivationSigmoid(), true, 300));
	}
	
	public FlexibleNetwork(TrainingSetHolder trainingSetHolder, BasicLayer... hiddenLayers) {
		this(trainingSetHolder.getInputDataSets().length > 0 ? trainingSetHolder.getInputDataSets()[0].length : 0, hiddenLayers);
		trainingSet = new BasicMLDataSet(trainingSetHolder.getInputDataSets(), trainingSetHolder.getExpectedOutcomes());
	}
	
	public FlexibleNetwork(int numberOfInputs, BasicLayer... hiddenLayers) {
		network = new BasicNetwork();
		network.addLayer(new BasicLayer(null, false, numberOfInputs));
		for (BasicLayer hiddenLayer : hiddenLayers) {
			network.addLayer(hiddenLayer);
		}
		network.addLayer(new BasicLayer(new ActivationLOG(), true, 1));
		network.getStructure().finalizeStructure();
		network.reset();
	}
	
	
	public BasicNetwork train(double targetErrorRate) {
		final MLTrain train = new ResilientPropagation(network, trainingSet);
		
		int epoch = 1;
		 
		double lowestError = 999d;
		
		do {
			train.iteration();
			if (train.getError() < lowestError) {
				LOG.info("Epoch #" + epoch + " Error:" + train.getError());
				lowestError = train.getError();
			}
			epoch++; 
		} while(train.getError() > targetErrorRate);
		
		return network;
	}

	public BasicNetwork getNetwork() {
		return network;
	}

	public void setNetwork(BasicNetwork network) {
		this.network = network;
	}

	public MLDataSet getTrainingSet() {
		return trainingSet;
	}

	public void setTrainingSet(MLDataSet trainingSet) {
		this.trainingSet = trainingSet;
	}
}
